#!/usr/bin/env python
from __future__ import with_statement

import BeautifulSoup

import _pyinstaweb

DEFAULT_URL = 'http://localhost'

class HtmlParser(_pyinstaweb.HtmlParser):
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

HtmlFilterAdapter = _pyinstaweb.HtmlFilterAdapter
CloseStyle = _pyinstaweb.CloseStyle

class XPathHelper(object):
    @staticmethod
    def calcXPath(tag_or_attr):
        tag = tag_or_attr
        path = []

        while tag is not None:
            if hasattr(tag, 'tagName'):
                path.append(tag.tagName)

            tag = tag.parent

        path.reverse()

        xpath = '/' + '/'.join(path)

        if hasattr(tag_or_attr, 'name'):
            xpath += '[@%s]' % tag_or_attr.name

        return xpath

_pyinstaweb.HtmlNode.xpath = property(lambda self: XPathHelper.calcXPath(self))
_pyinstaweb.HtmlAttribute.xpath = property(lambda self: XPathHelper.calcXPath(self))

class BaseExtractor(object):
    def __init__(self, name):
        self.name = 'extractor.' + name

    def result(self):
        raise NotImplementedError("Override Me")

    def extract(self, html, url=None):
        self.parser = HtmlParser(url)
        self.adapter = HtmlFilterAdapter(self.parser, self, self.name)
        self.parser.feed(html)
        self.parser.flush()

        return self.result()

class TextExtractor(BaseExtractor):
    TAG_ATTRS = {
        'a': ['name'],
        'img': ['alt'],
        'meta': ['content', 'keywords', 'description'],
        'table': ['summary'],
    }

    class TextResult(object):
        def __init__(self, extractor):
            self.extractor = extractor

        def __iter__(self):
            return self.extractor.text.__iter__()

        def __str__(self):
            return ''.join([value for xpath, value in self.extractor.text])

        def __repr__(self):
            return ','.join(["%s: %s" % (tag.xpath, value) for tag, value in self.extractor.text])

    def __init__(self, attrs=TAG_ATTRS):
        BaseExtractor.__init__(self, 'text')

        self.attrs = attrs
        self.text = []

    def handle_starttag(self, tag):
        names = self.attrs.get(tag.tagName)

        if names:
            for i in range(len(tag)):
                attr = tag.getAttribute(i)
                attr.tag = tag

                if attr.name in names:
                    self.text.append((attr, attr.value))

    def handle_data(self, data):
        self.text.append((data, data.contents))

    def handle_comment(self, comment):
        self.text.append((comment, comment.contents))

    def result(self):
        return TextExtractor.TextResult(self)

def extract_text(html, url=DEFAULT_URL):
    return TextExtractor().extract(html, url)

class HTMLParserBuilder(object):
    def __init__(self, soup, url=DEFAULT_URL):
        self.soup = soup
        self.parser = HtmlParser(url)

    def reset(self):
        self.parser.reset()
        self.adapter = HtmlFilterAdapter(self.parser, self, 'BeautifulSoup')

    def feed(self, html):
        self.parser.feed(html.encode('utf-8'))
        self.parser.close()

    def handle_starttag(self, tag):
        attrs = [(attr.name, attr.value) for attr in [tag.getAttribute(i) for i in range(len(tag))]]

        if tag.tagName == 'meta':
            self.soup.extractCharsetFromMeta(attrs)
        else:
            self.soup.unknown_starttag(tag.tagName, attrs)

    def handle_endtag(self, tag):
        self.soup.unknown_endtag(tag.tagName)

    def handle_data(self, data):
        self.soup.handle_data(data.contents.decode('utf-8'))

    def _toStringSubclass(self, text, subclass):
        """Adds a certain piece of text to the tree as a NavigableString subclass."""
        self.soup.endData()
        self.soup.handle_data(text.decode('utf-8'))
        self.soup.endData(subclass)

    def handle_comment(self, comment):
        "Handle comments as Comment objects."
        self._toStringSubclass(comment.contents, BeautifulSoup.Comment)

    def handle_decl(self, decl):
        "Handle DOCTYPEs and the like as Declaration objects."
        self._toStringSubclass(decl.contents, BeautifulSoup.Declaration)

    def handle_ie_decl(self, decl):
        self._toStringSubclass(decl.contents, BeautifulSoup.Comment)

    def handle_cdata(self, cdata):
        "Handle CDATA declaration as a CData object."
        self._toStringSubclass(cdata.contents, BeautifulSoup.CData)
